package compilers.AST.expression;

import java.io.PrintStream;
import java.util.Iterator;
import java.util.LinkedList;

import compilers.AST.Node;
import compilers.AST.types.MyBool;
import compilers.AST.types.MyType;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTableScope;

public class Logical extends Node {

	public final static int AND = 1;
	public final static int OR = 2;
	
	private Relation fFirstRelation;
	private LinkedList<Integer> fOperators;
	private LinkedList<Relation> fSecondRelations;
	
	public Logical() {
		fOperators = new LinkedList<Integer>();
		fSecondRelations = new LinkedList<Relation>();
	}
	
	public Logical(int lineNr) {
		super(lineNr);
		fOperators = new LinkedList<Integer>();
		fSecondRelations = new LinkedList<Relation>();
	}
	
	public void print(int identLevel) {
		println(identLevel, "Logical");
		fFirstRelation.print(identLevel+1);
		if (!fOperators.isEmpty()) {
			Iterator<Integer> itOp = fOperators.iterator();
			Iterator<Relation> itRel = fSecondRelations.iterator();
			while (itOp.hasNext()) {
				String opName = "";
				switch (itOp.next()) {
					case AND:
						opName = "&&";
						break;
					case OR:
						opName = "||";
						break;
				}
				println(identLevel+1, "Comparator = " + opName);
				itRel.next().print(identLevel+1);
			}
		}
	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		fFirstRelation.checkSemantics(currentScope);
		if (!fOperators.isEmpty()) {
			if (fFirstRelation.getResultType(currentScope) != TypeSpecifier.BOOLEAN) {
				throw new SemanticalException("The type in the logical expression is not of the requested boolean type.", fLineNr);
			} 
			Iterator<Relation> itRel = fSecondRelations.iterator();
			while (itRel.hasNext()) {
				if (itRel.next().getResultType(currentScope) != TypeSpecifier.BOOLEAN) {
					throw new SemanticalException("The type in the logical expression is not of the requested boolean type.", fLineNr);
				}
			}
		}
	}
	
	public void addOperator(int operator) {
		fOperators.add(operator);
	}
	
	public void setFirstRelation(Relation relation) {
		fFirstRelation = relation;
	}
	
	public void addSecondRelation(Relation relation) {
		fSecondRelations.add(relation);
	}
	
	public int getResultType(SymbolTableScope currentScope) {
		if (!fOperators.isEmpty()) {
			return TypeSpecifier.BOOLEAN;
		} else {
			return fFirstRelation.getResultType(currentScope);
		}
	}
	
	public int getOriginalType(SymbolTableScope currentScope) {
		if (!fOperators.isEmpty()) {
			return TypeSpecifier.BOOLEAN;
		} else {
			return fFirstRelation.getOriginalType(currentScope);
		}
	}
	
	public boolean isConstant(SymbolTableScope currentScope) {
		if (fOperators.isEmpty()) {
			return fFirstRelation.isConstant(currentScope);
		} else {
			if (!fFirstRelation.isConstant(currentScope)) {
				return false;
			}
			Iterator<Relation> itRel = fSecondRelations.iterator();
			while (itRel.hasNext()) {
				if (!itRel.next().isConstant(currentScope)) {
					return false;
				}
			}
			return true;
		}
	}
	
	public boolean isArray(SymbolTableScope currentScope) {
		if (fOperators.isEmpty()) {
			return fFirstRelation.isArray(currentScope);
		} else {
			return false;
		}
	}
	
	public String getArrayIdent(SymbolTableScope currentScope) {
		return fFirstRelation.getArrayIdent(currentScope);
	}
	
	public MyType calculateValue(SymbolTableScope currentScope) {
		if(fOperators.isEmpty()) {
			return fFirstRelation.calculateValue(currentScope);
		} else {
			Iterator<Integer> itOp = fOperators.iterator();
			Iterator<Relation> itRel = fSecondRelations.iterator();
			MyBool result = (MyBool) fFirstRelation.calculateValue(currentScope);
			while (itRel.hasNext()) {
				Relation rel = itRel.next();
				if(itOp.next() == AND) {
					result = (MyBool) result.and(rel.calculateValue(currentScope));
				} else {
					result = (MyBool) result.div(rel.calculateValue(currentScope));
				}
			}
			return result;
		}
	}
	
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope) {	
		// First relation
		if (fFirstRelation.isConstant(currentScope)) {
			TypeSpecifier firstResultType = new TypeSpecifier(fFirstRelation.getResultType(currentScope));
			pGen.load(firstResultType.getPType(currentScope), fFirstRelation.calculateValue(currentScope).valueToString());
		} else {
			fFirstRelation.generatePcode(pGen, currentScope);
		}
		
		// Second relations
		if (!fOperators.isEmpty()) {
			Iterator<Integer> itOp = fOperators.iterator();
			Iterator<Relation> itRel = fSecondRelations.iterator();
			while (itRel.hasNext()) {
				Relation rel = itRel.next();
				if (rel.isConstant(currentScope)) {
					pGen.load(TypeSpecifier.getPType(rel.getResultType(currentScope)), rel.calculateValue(currentScope).valueToString());
				} else {
					rel.generatePcode(pGen, currentScope);
				}
			
				if (itOp.next() == AND) {
					pGen.and();
				} else {
					pGen.or();
				}
			}
		}
	}
}
